最終更新日:191218 原本2015-11-08

IE8 で form のデータを非同期で送信して json のレスポンスを受け取る

form が持つデータを JavaScript から取得するための API として、 FormData というクラスが存在する。
これを利用すると、 Ajax でファイルをアップロードすることができるようになる。

ところが、この FormData は IE8, 9 では利用できない(IE10 以上なら利用できる)。

IE8 でも非同期で form のデータを送信したい場合は、 <iframe> タグを利用した(若干トリッキーな)方法が存在する。

環境

Web ブラウザ

IE11 (開発者モードで IE8 をエミュレートさせて確認)

AP サーバー

Payara 4.1.154

Java

JDK 1.8.0_60

仕組み的な

ie8_ajax_form.JPG

  • <form> タグに target 属性を設定する。
  • target には、同じ <html> 内に存在する <iframe>name を指定する。
  • <iframe> タグは、 display: none; などを使って見えないようにしておく。
  • <form> をサブミットすると、リクエストの結果は target で指定した <iframe> に出力される。
  • このため、画面遷移は起こらず Ajax で非同期通信したかのような動作になる。

実装的な

クライアント

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>sample</title>
    <style>
      .invisible {
        display: none;
      }
    </style>
  </head>
  <body>
    <form id="form" action="sample-servlet" method="POST" target="_frame">
      <input type="text" name="text" />
      <input type="submit" value="Submit" />

      <iframe id="frame" name="_frame" class="invisible"></iframe>
    </form>

    <script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>
script.js
$(function() {
    $('#frame').on('load', function() {
        var json = $(this).contents().text();
        var response = $.parseJSON(json);
        alert('foo=' + response.foo + ', bar=' + response.bar);
    });
});

サーバー

SampleServlet.java
package jta_sample;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/sample-servlet")
public class SampleServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try (BufferedReader br = req.getReader();) {
            br.lines().forEach(System.out::println);
        }

        // ★ Content-Type に text/plain を指定
        resp.setHeader("Content-Type", "text/plain");

        try (PrintWriter pw = resp.getWriter();) {
            pw.println("{\"foo\": \"xxx\", \"bar\": true}");
        }
    }
}

動作確認

ie8_ajax_form.JPG

開発者モードで IE8 にして実行。

↓サブミット

ie8_ajax_form.JPG

サーバー側出力

2015-11-08T23:18:16.533+0900|情報: text=abcdefg

説明

レスポンスの受け取りには

script.js
    $('#frame').on('load', function() {
        var json = $(this).contents().text();
        var response = $.parseJSON(json);
        alert('foo=' + response.foo + ', bar=' + response.bar);
    });
  • <iframe>load イベントを監視することで、サーバーからのレスポンスを受け取れるようになる。
  • ステータスコードとかは参照できないので、レスポンスに成功かエラーか分かる識別子を渡す必要がありそう。

コンテンツタイプに text/plain を指定する

SampleServlet.java
    resp.setHeader("Content-Type", "text/plain");

json を返すんだから application/json にしたいところだが、そうすると IE はファイルのダウンロードを開始してしまう。

Content-Type を application/json にした場合

ie8_ajax_form.JPG

気持ち悪いけど、 text/plain にしてあげる必要があるっぽい。
(JAX-RS を使っていると、 JSON 変換のための MessageBodyWriter と連携するためにメソッドを @Produces(MediaType.APPLICATION_JSON) でアノテートしたくなるけど、それするとファイルダウンロードになってしまうので歯がゆい)

参考